package chapter8;

import chapter8.model.Student;
import redis.clients.jedis.Jedis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class Chapter8Demo3 {
    static Connection conn;
    static Jedis jedis;

    public static void main(String[] args) throws InterruptedException {
        //mysqlDemo();
        //mysqlRedisDemo();
        //improperUse();
        //avoidPenetrateDemo();
        randomExpireTimeDemo();
    }

    /**
     * MySQL演示函数
     * 该函数演示了如何使用Java连接到MySQL数据库，并从数据库中检索数据
     * 它首先加载数据库驱动，然后创建与数据库的连接，执行查询并打印结果
     * 最后，它关闭数据库连接和相关资源
     */
    static void mysqlDemo() {
        // 定义MySQL数据库的JDBC驱动
        String driver = "com.mysql.cj.jdbc.Driver";
        // 定义数据库连接URL
        String url = "jdbc:mysql://localhost:3306/redisDemo";
        // 定义数据库用户名
        String user = "root";
        // 定义数据库密码
        String password = "123456";
        try {
            // 加载并注册JDBC驱动
            Class.forName(driver);
            // 建立与数据库的连接
            Connection conn = java.sql.DriverManager.getConnection(url, user, password);
            // 准备SQL语句，用于查询学生信息
            PreparedStatement stmt = conn.prepareStatement("select * from student");
            // 执行查询并获取结果集
            ResultSet rs = stmt.executeQuery();
            // 遍历结果集，打印每个学生的详细信息
            while (rs.next()) {
                System.out.print("id:" + rs.getString("id") + ",");
                System.out.print("name:" + rs.getString("name") + ",");
                System.out.print("age:" + rs.getInt("age") + ",");
                System.out.println("score:" + rs.getString("score"));
            }
            // 关闭结果集、PreparedStatement和数据库连接，释放资源
            rs.close();
            stmt.close();
            conn.close();
        } catch (Exception e) {
            // 打印异常信息，用于调试和错误追踪
            System.out.println(e);
        }
    }


    /**
     * 数据库与Redis示例函数
     * 此函数展示了如何从数据库中获取学生信息，并打印出来
     * 它首先进行初始化，然后尝试从数据库中获取学生信息，并打印学生的ID、姓名、年龄和分数
     * 注意：这个函数假设存在一个初始化函数init()和一个用于从数据库获取学生信息的函数getStudent(int id)
     */
    static void mysqlRedisDemo() {
        init(); // 初始化环境或资源，为数据库操作做准备
        for (int i = 1; i <= 2; i++) { // 循环两次，尝试获取学生信息，这里循环的次数可能基于特定需求或示例目的设定
            Student student = getStudent(1); // 从数据库中获取指定ID为1的学生信息
            if (student != null) { // 检查获取到的学生信息是否为空，不为空则打印信息
                System.out.print("id=" + student.getId() + ",");
                System.out.print("name=" + student.getName() + ",");
                System.out.print("age=" + student.getAge() + ",");
                System.out.println("score=" + student.getScore()); // 打印学生的各项信息
            }
        }

        // 缓存穿透现象模拟，相当于高并发场景直接查询数据库
        for (int i = 1; i <= 10000; i++) {
            int idNotExists = 10;
            Student student = getStudent(idNotExists); // 从数据库中获取指定ID为10(不存在)的学生信息
            if (student != null) {
                System.out.print("id=" + student.getId() + ",");
                System.out.print("name=" + student.getName() + ",");
                System.out.print("age=" + student.getAge() + ",");
                System.out.println("score=" + student.getScore()); // 打印学生的各项信息
            }
        }
    }

    static void init() {
        // 定义MySQL数据库的JDBC驱动
        String driver = "com.mysql.cj.jdbc.Driver";
        // 定义数据库连接URL
        String url = "jdbc:mysql://localhost:3306/redisDemo";
        // 定义数据库用户名
        String user = "root";
        // 定义数据库密码
        String password = "123456";
        try {
            // 加载并注册JDBC驱动
            Class.forName(driver);
            // 建立与数据库的连接
            conn = java.sql.DriverManager.getConnection(url, user, password);
            jedis = new Jedis("localhost", 6379);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    /**
     * 根据学生ID获取学生信息
     * 首先尝试从Redis缓存中获取学生信息，如果缓存存在，则从Redis中读取并返回学生信息
     * 如果Redis中不存在该学生信息，则从MySQL数据库中查询，并将结果缓存到Redis中
     *
     * @param id 学生ID
     * @return 返回学生信息，如果找不到对应ID的学生，则返回null
     */
    static Student getStudent(int id) {
        // 构建Redis中的学生信息键名
        String key = "student:" + id;
        // 检查Redis中是否存在该学生信息
        if (jedis.exists(key)) {
            System.out.println("从redis中获取数据");
            // 从Redis中获取学生信息，并映射到Student对象中
            List<String> list = jedis.lrange(key, 0, 2);
            Student student = new Student();
            student.setId(id);
            student.setName(list.get(0));
            student.setAge(Integer.parseInt(list.get(1)));
            student.setScore(Float.parseFloat(list.get(2)));
            return student;
        }
        System.out.println("从mysql中获取数据");
        try {
            // 准备SQL语句，用于查询学生信息
            PreparedStatement stmt = conn.prepareStatement("select * from student where id=?");
            stmt.setInt(1, id);
            // 执行查询并获取结果集
            ResultSet rs = stmt.executeQuery();
            // 遍历结果集，打印每个学生的详细信息
            if (rs.next()) {
                // 创建Student对象，并填充从数据库查询到的信息
                Student student = new Student();
                student.setId(id);
                student.setName(rs.getString("name"));
                student.setAge(rs.getInt("age"));
                student.setScore(rs.getFloat("score"));
                // 将查询到的学生信息存入Redis缓存
                jedis.rpush(key, student.getName(), String.valueOf(student.getAge()), String.valueOf(student.getScore()));
                return student;
            } else {
                System.out.println("mysql数据库中没有找到该学生");
            }
        } catch (SQLException e) {
            System.out.println(e);
        }
        return null;
    }

    /**
     * 演示Redis操作中的不当用法
     * 此方法展示在使用Redis进行大量数据操作时，不当的内存管理可能导致的潜在问题
     * 通过在循环中不断向Redis列表添加数据，可能导致JVM内存溢出
     */
    static void improperUse() {
        // 创建Redis客户端
        Jedis jedis = new Jedis("localhost", 6379);
        // 打印当前JVM空闲内存量，单位为M
        System.out.println((Runtime.getRuntime().freeMemory() >> 20) + "M");
        // 循环100000次，每次向Redis中添加数据
        for (int i = 0; i < 100000; i++) {
            // 创建一个包含随机字符串的键
            String key = "Stu:" + i;
            // 将键添加到Redis列表中
            jedis.rpush(key, String.valueOf(i));
        }
        // 再次打印JVM空闲内存量，单位为M
        System.out.println((Runtime.getRuntime().freeMemory() >> 20) + "M");
    }

    static void avoidPenetrateDemo() {
        init();
        // 缓存穿透现象模拟
        for (int i = 1; i <= 3; i++) {
            int idNotExists = 10;
            Student student = getStudentV2(idNotExists); // 从数据库中获取指定ID为10(不存在)的学生信息
            if (student != null) {
                System.out.print("id=" + student.getId() + ",");
                System.out.print("name=" + student.getName() + ",");
                System.out.print("age=" + student.getAge() + ",");
                System.out.println("score=" + student.getScore()); // 打印学生的各项信息
            }
        }
    }

    static Student getStudentV2(int id) {
        // 构建Redis中的学生信息键名
        String key = "studentV2:" + id;
        // 检查Redis中是否存在该学生信息
        if (jedis.exists(key)) {
            System.out.println("从redis中获取数据");
            // 从Redis中获取学生信息，并映射到Student对象中
            List<String> list = jedis.lrange(key, 0, 2);
            Student student = new Student();
            student.setId(id);
            student.setName(list.get(0));
            student.setAge(Integer.parseInt(list.get(1)));
            student.setScore(Float.parseFloat(list.get(2)));
            return student;
        }
        System.out.println("从mysql中获取数据");
        try {
            // 准备SQL语句，用于查询学生信息
            PreparedStatement stmt = conn.prepareStatement("select * from student where id=?");
            stmt.setInt(1, id);
            // 执行查询并获取结果集
            ResultSet rs = stmt.executeQuery();
            // 遍历结果集，打印每个学生的详细信息
            if (rs.next()) {
                // 创建Student对象，并填充从数据库查询到的信息
                Student student = new Student();
                student.setId(id);
                student.setName(rs.getString("name"));
                student.setAge(rs.getInt("age"));
                student.setScore(rs.getFloat("score"));
                // 将查询到的学生信息存入Redis缓存
                jedis.rpush(key, student.getName(), String.valueOf(student.getAge()), String.valueOf(student.getScore()));
                jedis.expire(key, 60 * 5); // 缓存5分钟
                return student;
            } else {
                jedis.rpush(key, "", "0", "0");
                jedis.expire(key, 60 * 30); // 缓存30分钟
                System.out.println("mysql数据库中没有找到该学生");
            }
        } catch (SQLException e) {
            System.out.println(e);
        }
        return null;
    }

    /**
     * 演示Redis键的随机过期时间设置
     * 本方法旨在展示如何在Redis中设置键，并为每个键分配一个随机的过期时间
     * 它首先清除Redis的所有现有数据，然后设置5个键，每个键的过期时间都是一个随机数加上一个固定值
     * 之后，它通过休眠6秒来模拟超时，然后检查每个键是否仍然存在
     *
     * @throws InterruptedException 如果线程在休眠期间被中断
     */
    static void randomExpireTimeDemo() throws InterruptedException {
        // 创建Jedis实例，连接到本地Redis服务器
        Jedis jedis = new Jedis("localhost", 6379);
        // 清除Redis中的所有数据，以便开始新的演示
        jedis.flushAll();
        // 定义一个固定的过期时间秒数
        int fixedTime = 5;
        // 循环5次，每次设置一个键和随机过期时间
        for (int i = 0; i < 5; i++) {
            // 生成一个0到2之间的随机数，作为随机过期时间部分
            int randomTime = (int) (Math.random() * 3);
            // 构造键名
            String key = "stu:" + i;
            // 设置键值对
            jedis.set(key, "value" + i);
            // 为键设置过期时间，是随机时间和固定时间之和
            jedis.expire(key, randomTime + fixedTime);
        }
        // 休眠6秒,模拟超时，以便观察键的过期效果
        Thread.sleep(6000);
        // 再次循环，检查并打印每个键是否存在
        for (int i = 0; i < 5; i++) {
            // 构造键名
            String key = "stu:" + i;
            // 检查键是否存在，并打印结果
            if (jedis.exists(key)) {
                System.out.println(key + " exists.");
            } else {
                System.out.println(key + " not exists.");
            }
        }
    }

}
